package com.gitee.cliveyuan.tools.http;

import com.gitee.cliveyuan.tools.CollectionTools;
import com.gitee.cliveyuan.tools.StringTools;
import com.gitee.cliveyuan.tools.cons.NetCons;
import com.gitee.cliveyuan.tools.exception.NetException;
import com.google.common.collect.Lists;
import org.apache.http.Header;
import org.apache.http.HttpEntity;
import org.apache.http.NameValuePair;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.conn.ConnectTimeoutException;
import org.apache.http.conn.HttpHostConnectException;
import org.apache.http.cookie.Cookie;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.BasicCookieStore;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.message.BasicNameValuePair;
import org.apache.http.util.EntityUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.IOException;
import java.net.SocketTimeoutException;
import java.net.UnknownHostException;
import java.util.List;
import java.util.Objects;

/**
 * httpClient 工具
 * <p>
 * Created by Clive at 2018/07/24.
 * Rebuilt by Clive at 2018/11/26.
 *
 * @since 2.0.0
 */
public class HttpClientTools extends AbstractHttpRequest {

    private static final Logger logger = LoggerFactory.getLogger(HttpClientTools.class);

    private String encoding = "UTF-8";
    private boolean isPayload;
    private String payload;
    private boolean throwStatusException = true;

    private HttpClientTools() {
        timeout = NetCons.HTTP_CLIENT_TIME_OUT;
    }

    // region public methods

    /**
     * 构建HttpClient工具
     *
     */
    public static HttpClientTools build() {
        return new HttpClientTools();
    }

    @Override
    public String get() throws NetException {
        HttpResp httpResp = sendGet();
        return httpResp.getContent();
    }

    @Override
    public String post() throws NetException {
        HttpResp httpResp = sendPost();
        return httpResp.getContent();
    }

    @Override
    public HttpResp executeGet() throws NetException {
        return sendGet();
    }

    @Override
    public HttpResp executePost() throws NetException {
        return sendPost();
    }

    /**
     * 编码方式
     *
     * @param encoding 默认UTF-8
     */
    public HttpClientTools encoding(String encoding) {
        this.encoding = encoding;
        return this;
    }

    /**
     * 请求入参是json格式时设置此字段为true
     *
     * @param isPayload
     */
    public HttpClientTools isPayload(boolean isPayload) {
        this.isPayload = isPayload;
        return this;
    }

    /**
     * 设置payload内容
     *
     * @param payload
     */
    public HttpClientTools payload(String payload) {
        this.payload = payload;
        return this;
    }
    /**
     * 当http status != 200 时不抛出异常
     *
     */
    public AbstractHttpRequest notThrowStatusException() {
        this.throwStatusException = false;
        return this;
    }

    // endregion public methods


    // region private methods

    private HttpResp sendPost() throws NetException {
        validate();
        BasicCookieStore cookieStore = new BasicCookieStore();
        try (CloseableHttpClient httpClient = HttpClients.custom().setDefaultCookieStore(cookieStore).build()) {

            HttpPost httpPost = new HttpPost(url);
            if (isPayload) {
                StringEntity requestEntity = new StringEntity(payload != null ? payload : "", encoding);
                requestEntity.setContentEncoding(encoding);
                httpPost.setEntity(requestEntity);
                httpPost.setHeader("Content-type", "application/json");
            } else {
                List<NameValuePair> nameValuePairs = Lists.newArrayList();
                if (params != null) {
                    params.forEach((k, v) ->
                            nameValuePairs.add(new BasicNameValuePair(k, v))
                    );
                    httpPost.setEntity(new UrlEncodedFormEntity(nameValuePairs, encoding));
                }
            }
            RequestConfig requestConfig = RequestConfig.custom()
                    .setConnectTimeout(timeout)
                    .setConnectionRequestTimeout(timeout)
                    .setSocketTimeout(timeout).build();
            httpPost.setConfig(requestConfig);
            if (addDefaultHeaders) {
                NetCons.DEFAULT_HEADERS.forEach(httpPost::setHeader);
            }
            if (CollectionTools.isNotEmpty(headers)) {
                headers.forEach(httpPost::setHeader);
            }
            if (CollectionTools.isNotEmpty(cookies)) {
                httpPost.setHeader("cookie", StringTools.map2CookieStr(cookies));
            }
            try (CloseableHttpResponse response = httpClient.execute(httpPost)) {
                return parseResponse(response, cookieStore);
            }
        } catch (NetException e) {
            throw e;
        } catch (UnknownHostException e) {
            throw NetException.unknownHost();
        } catch (SocketTimeoutException | ConnectTimeoutException e) {
            throw NetException.timeout(timeout);
        } catch (HttpHostConnectException e) {
            throw NetException.connectionRefused(url);
        } catch (Exception e) {
            //e.printStackTrace();
            logger.error("request error", e);
            throw NetException.unKnowError();
        }
    }

    private HttpResp sendGet() throws NetException {
        validate();
        BasicCookieStore cookieStore = new BasicCookieStore();
        try (CloseableHttpClient httpClient = HttpClients.custom().setDefaultCookieStore(cookieStore).build()) {
            url = InnerTools.getQueryUrl(url, params);
            HttpGet httpGet = new HttpGet(url);
            RequestConfig requestConfig = RequestConfig.custom()
                    .setConnectTimeout(timeout)
                    .setConnectionRequestTimeout(timeout)
                    .setSocketTimeout(timeout).build();
            httpGet.setConfig(requestConfig);
            if (addDefaultHeaders) {
                NetCons.DEFAULT_HEADERS.forEach(httpGet::setHeader);
            }
            if (CollectionTools.isNotEmpty(headers)) {
                headers.forEach(httpGet::setHeader);
            }
            if (CollectionTools.isNotEmpty(cookies)) {
                httpGet.setHeader("cookie", StringTools.map2CookieStr(cookies));
            }
            try (CloseableHttpResponse response = httpClient.execute(httpGet)) {
                return parseResponse(response, cookieStore);
            }
        } catch (NetException e) {
            throw e;
        } catch (UnknownHostException e) {
            throw NetException.unknownHost();
        } catch (SocketTimeoutException | ConnectTimeoutException e) {
            throw NetException.timeout(timeout);
        } catch (HttpHostConnectException e) {
            throw NetException.connectionRefused(url);
        } catch (Exception e) {
            logger.error("request error", e);
            e.printStackTrace();
            throw NetException.unKnowError();
        }
    }

    private HttpResp parseResponse(CloseableHttpResponse response, BasicCookieStore cookieStore) throws IOException, NetException {
        List<Cookie> cookies = cookieStore.getCookies();
        HttpResp httpResp = HttpResp.create();
        cookies.forEach(cookie ->
                httpResp.addCookie(cookie.getName(), cookie.getValue())
        );
        httpResp.setStatusCode(response.getStatusLine().getStatusCode());
        if (this.throwStatusException && response.getStatusLine().getStatusCode() != 200) {
            throw NetException.httpStatusError(response.getStatusLine().getStatusCode());
        }
        HttpEntity entity = response.getEntity();
        if (Objects.nonNull(entity)) {
            Header[] headers = response.getAllHeaders();
            if (CollectionTools.isNotEmpty(headers)) {
                for (Header header : headers) {
                    httpResp.addHeader(header.getName(), header.getValue());
                }
            }
            httpResp.setContent(EntityUtils.toString(entity, encoding));
        }
        return httpResp;
    }


    // endregion private methods


}
